package com.bdqn.dao;

import com.alibaba.fastjson.JSON;
import com.bdqn.util.UnsupportedTypeException;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * 数据库基类
 *
 * @author LILIBO
 * @since 2021-11-24
 */
public class BaseDao {

	private static Logger logger = Logger.getLogger(BaseDao.class);

	/**
	 * 配置文件位置
	 */
	private static String config = "source.properties";

	/**
	 * 驱动名称
	 */
	private static String driver;
	/**
	 * 连接URL
	 */
	private static String url;
	/**
	 * 用户名
	 */
	private static String username;
	/**
	 * 密码
	 */
	private static String password;

	/**
	 * 静态代码块，在类加载的时候执行
	 */
	static {
		init(); // 加载配置文件，初始化参数
	}

	/**
	 * 加载配置文件，初始化参数
	 */
	public static void init() {
		Properties params = new Properties();
		// 加载配置文件到文件流
		InputStream is = BaseDao.class.getClassLoader().getResourceAsStream(config);
		try {
			params.load(is); // 从文件流加载配置到内存之中
		} catch (IOException e) {
			e.printStackTrace();
		}
		// 通过getProperty方法获得配置文件中配置的值
		driver = params.getProperty("driver");
		url = params.getProperty("url");
		username = params.getProperty("username");
		password = params.getProperty("password");
	}

	/**
	 * 获得数据库连接
	 * 
	 * @return
	 */
	public Connection getConnection() {
		Connection conn = null; // 数据库连接对象
		try {
			Class.forName(driver); // 加载驱动
			conn = DriverManager.getConnection(url, username, password); // 获得连接
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return conn;
	}

	/**
	 * 关闭数据库连接
	 * 
	 * @param conn  数据库连接对象
	 * @param pstmt 数据库操作对象
	 * @param rs    数据结果集对象
	 */
	public void closeAll(Connection conn, PreparedStatement pstmt, ResultSet rs) {
		if (rs != null) {
			try {
				rs.close(); // 关闭数据结果集对象
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if (pstmt != null) {
			try {
				pstmt.close(); // 关闭数据库操作对象
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		if (conn != null) {
			try {
				conn.close(); // 关闭数据库连接对象
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 通用增删改方法
	 * 
	 * @param sql 预编译的SQL语句（可能会带?占位符）
	 * @param params 参数列表（可变长参数，与?个数一致）
	 * @return 受影响的行数
	 */
	public int executeUpdate(String sql, Object... params) {
		logger.debug(sql + "\n" + JSON.toJSONString(params));
		PreparedStatement pstmt = null; // 数据库操作对象
		int num = 0; // 受影响的行数，用于返回
		Connection conn = this.getConnection(); // 通过通用的方法获得数据库连接
		try {
			pstmt = conn.prepareStatement(sql); // 初始化数据库操作对象
			if (params != null) { // 为SQL语句设置参数，多少个问号"?"就设置多少个参数一一对应
				for (int i = 0; i < params.length; i++) {
					if (params[i] != null) {
						pstmt.setObject(i + 1, params[i]); // 循环给问号的位置（从1开始）依次设置参数值
					}
				}
			}
			num = pstmt.executeUpdate(); // 执行数据库增删改操作，返回受影响的行数

			logger.debug("Update : " + num);
		} catch (SQLException e) {
			e.printStackTrace();
			logger.error(e.getMessage());
		} finally {
			this.closeAll(conn, pstmt, null); // 资源统一回收
		}
		return num;
	}

	/**
	 * 通用查询方法
	 *
	 * @param targetType 查询封装的目标类型（查询封装到哪个类）
	 * @param sql 预编译的SQL语句（可能会带?占位符）
	 * @param params 参数列表（可变长参数，与?个数一致）
	 * @return 封装好的数据对象集合
	 */
	public <T> List<T> executeQuery(Class<T> targetType, String sql, Object... params) {
		logger.debug(sql + "\n" + JSON.toJSONString(params));
		List<T> list = new ArrayList<>(); // 最终返回的结果
		Connection conn = this.getConnection(); // 获得数据库连接
		PreparedStatement pstmt = null; // 初始化数据库操作对象
		ResultSet rs = null; // 查询结果集
		try {
			pstmt = conn.prepareStatement(sql); // 获得数据库操作对象
			if (params != null) { // 为SQL语句设置参数，多少个问号"?"就设置多少个参数一一对应
				for (int i = 0; i < params.length; i++) {
					if (params[i] != null) {
						pstmt.setObject(i + 1, params[i]); // 循环给问号的位置（从1开始）依次设置参数值
					}
				}
			}
			// 执行查询
			rs = pstmt.executeQuery();

			// ------ 原来的实现方式，从结果集中读取到一行数据就实例化一个对象，取出列数据针对性的给对象属性赋值 ------

			/*
			// 处理结果集中的数据
			while (rs.next()) {
				Student student = new Student(); // 有一行数据就创建一个学生对象
				student.setId(rs.getInt(1));
				student.setName(rs.getString(2));
				student.setSex(rs.getInt(3));
				student.setAddress(rs.getString(4));
				student.setBirthday(rs.getString(5));
				list.add(student); // 将对象添加到集合
			}
			*/

			// ------ 通过反射实现通用的数据列与实体映射赋值 ------

			// 结果集列信息Map集合
			Map<String, String> columnMap = new HashMap<>();
			// 获得ResultSet对象的列的数量、类型和属性
			ResultSetMetaData metaData = rs.getMetaData();
			// 获得查询结果集总列数
			int columnCount = metaData.getColumnCount();
			for (int i = 1; i <= columnCount; i++) {
				// 将列名及对应的类型信息保存到Map集合
				columnMap.put(metaData.getColumnName(i), metaData.getColumnTypeName(i));
			}

			// 处理结果集中的数据
			while (rs.next()) {
				// 存放查询结果数据
				Map<String, String[]> resultMap = new HashMap<>();

				// 通过反射，使用Class<T>构造一个对象
				T target = targetType.newInstance();
				// 获取Class<T>里面所有的public方法
				Method[] methods = targetType.getMethods();
				if (methods != null && methods.length > 0) {
					for (Method method : methods) {
						// 找到以set开头的方法
						if (method.getName().startsWith("set")) {
							// 获取方法的参数列表
							Class<?>[] args = method.getParameterTypes();
							// 找到只有一个参数的方法
							if (args.length == 1) {
								// 获取对象属性的set方法，将结果集中的列对应赋值给属性
								// setName -> name
								String paramName = method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4);
								// 查看所有参数的Map中是否存在key和对象的属性匹配的
								if (columnMap.containsKey(paramName)) {
									try {
										// 将String类型的字段数据转换为对应实体属性类型
										Object value = parseValue(rs.getString(paramName), args[0]);
										// 将查询结果的字段值与实体类型属性通过setXXX()方法进行映射绑定（通过反射使用目标对象target调用method方法，传递一个参数value）
										method.invoke(target, value);
									} catch (ParseException e) {
										System.out.println("参数转换错误：" + e.getMessage());
										e.printStackTrace();
										throw e;
									}
								} else if (Boolean.class == args[0] || boolean.class == args[0]) {
									// 如果是boolean类型，不存在表示false
									method.invoke(target, false);
								}
							}
						}
					}
				}

				// 将对象添加到集合
				list.add(target); // 将通过反射创建的对象添加到集合
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (ParseException e) {
			e.printStackTrace();
		} finally {
			// 释放资源
			this.closeAll(conn, pstmt, rs);
		}
		logger.debug("Result : (count = " + list.size() + ")\n" + JSON.toJSONString(list));
		return list;
	}

	/**
	 * 将String类型的参数值转换为相关属性的类型
	 *
	 * @param value 要转换的数据
	 * @param type 对应的类型
	 * @return
	 */
	private Object parseValue(String value, Class type) throws ParseException {
		if (value == null) {
			return null;
		}
		if (String.class == type) {
			return value;
		}
		if (boolean.class == type || Boolean.class == type) {
			if ("".equals(value) || "false".equals(value)) {
				return false;
			}
			return true;
		}
		if (java.util.Date.class == type) {
			return new SimpleDateFormat("yyyy-MM-dd").parse(value);
		}
		if (char.class == type || Character.class == type) {
			return value.charAt(0);
		}
		if (int.class == type || Integer.class == type) {
			return Integer.valueOf(value);
		}
		if (byte.class == type || short.class == type || long.class == type || float.class == type || double.class == type) {
			try {
				// byte | Byte -> Byte
				type = Class.forName("java.lang." + type.getName().substring(0, 1).toUpperCase() + type.getName().substring(1));
				if (type.getName().startsWith("java.lang.") || type.getName().startsWith("java.math.")) {
					try {
						// long l = new Long("9"); // 9
						// 获取参数类型的构造函数（带一个String参数）
						return type.getConstructor(String.class).newInstance(value);
					} catch (Exception e) {
						throw new UnsupportedTypeException(e);
					}
				}
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
}